package jia.temp;

import jia.game.GenericFlags;
import jia.core.GameStateManager;
import jia.ai.Course;
import jia.game.combat.CombatFrame;
import jia.game.combat.CombatGlobals;
import jia.core.calculations.Area;
import jia.core.calculations.Combat;
import jia.core.calculations.Dice;
import jia.core.characters.CombatComparator;
import jia.core.characters.GameCharacter;
import jia.core.items.Weapon;
import jia.core.items.Item;
import jia.core.state.CombatState;
import org.apache.log4j.Logger;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;

/**
 * YES, I know this one UGLY class...  But, as the name will indicate its a PRIMITIVE
 * game runner, designed only to test basic melee combat process using the values
 * on the GameCharacter objects.
 * <p/></p/>
 * It is highly likely this class will either be HEAVILY refactored or simply go away in favor
 * of a cleaner and easier to follow solution.
 */
public class PrimitiveCombatRunner extends Canvas {
    private static final Logger log = Logger.getLogger(PrimitiveCombatRunner.class);
    private static ArrayList<CombatState> combatants = new ArrayList<CombatState>();
    private Course course = new Course();
    private Area area = new Area();
    
    
    int width;
    int height;
    int rows = 25;
    int cols = 25;
    
    protected int gridWidth = 50;
    protected int gridHeight = 50;
    
    BufferedImage grid;
    
    public PrimitiveCombatRunner() {
    }
    
    /**
     * Primary method that runs the process
     */
    public void runCombat(GameStateManager gameState) {
        PrimitiveCombatData combatData = new PrimitiveCombatData(gameState);
        //Set up the combatants with weapons and their placements on the grid
        combatants = combatData.setupCombatants();
        
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        CombatFrame mainAppFrame = new CombatFrame(ge.getDefaultScreenDevice());
        
        mainAppFrame.initGame(combatants);
        mainAppFrame.setMode(1);
        mainAppFrame.start();
        
        //set blocked squares:
        Point[] blockedSquares = {new Point(2,6), new Point(3,6), new Point(4,6), new Point(5,6), new Point(6,6), new Point(7,6), new Point(8,6), new Point(9,6), new Point(10,6), new Point(11,6), new Point(12,6), new Point(13,6)};
        
        
        //SORT THE LIST BY INITIATIVE SCORE.
        Collections.sort(combatants, new CombatComparator());
        
        //TODO:  UNECESSARY DEBUG LOOPER, CAN BE REMOVED LATER
        CombatState characterState;
        for (CombatState combatant : combatants) {
            characterState = combatant;
            
            
            //set Hitpoints for character
            //PrimitiveCombatData.setHP(characterState);
            
            //Show the generated character information in the log
            PrimitiveCombatData.sendCharacterInformationToLog(characterState, characterState.getCharacter().getReadiedWeapon());
            
            //choose an opponent
            chooseOpponent(characterState);
            
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            if (GenericFlags.errorMessages){
                log.error(e.getMessage());
            }
        }
        
        //DO THE COMBAT RUNNING UNTIL EITHER "TEAM" IS DEAD.
        do {
            if (GenericFlags.debugMessages) {
                log.debug("");
                log.debug("");
                log.debug("****************************************************************************");
                log.debug("Starting a combat round!");
                log.debug("****************************************************************************");
                log.debug("");
            }
            for (int i = 0; i < combatants.size(); i++) {
                characterState = (combatants.get(i));
                CombatState opponentState = Combat.getOpposingCharacterState(characterState, combatants);
                GameCharacter character = characterState.getCharacter();
                Weapon longsword = (Weapon)characterState.getCharacter().getReadiedWeapon();
                if (GenericFlags.debugMessages) {
                    log.debug("Character Name: " + character.getCharacterName());
                    log.debug("Character's Hit Points: " + character.getCharacterHP());
                    log.debug("Character's AC is: " + character.getArmorClass() + " - (" + character.getArmorBonus()+ ") of which is based on armor");
                }
                if (character.getIsDead()){
                    if (GenericFlags.debugMessages) {
                        log.debug(character.getCharacterName() + " is dead");
                    }
                }else{
                    if (GenericFlags.debugMessages) {
                        log.debug(character.getCharacterName() + " is alive");
                    }
                }
                
                
                //if this character is not dead, held, blind or flat-footed
                if (!character.getIsDead() && !characterState.isHeld() && !characterState.isFlatFooted() && !characterState.isBlind()) {
                    
                    Point newSquare;
                    ArrayList<Point> courseList;
                    if (!characterState.isPanicked()) {
                        //each player should make an meleeAttack against opponents who are not dead
                        if (characterState.getOpponentId() != 0 && !Combat.isOpponentDead(characterState, combatants)) {
                            
                            //determine if the character should move before making an attack
                            if (GenericFlags.debugMessages) {
                                log.debug("Checking to see if " + character.getCharacterName() + "'s opponent is in range");
                            }
                            if (!area.isInMeleeRange(characterState.getXY(), opponentState.getXY(), opponentState.getSurroundingSquares())) {
                                if (GenericFlags.debugMessages) {
                                    log.debug(character.getCharacterName() + " needs to move before making an attack");
                                }
                                
                                //plot a course to the opponent
                                courseList = this.course.plot(characterState.getXY(), opponentState.getXY(), blockedSquares);
                                
                                //loop through the course, setting the characters placement with each "step"
                                for (Point aCourseList : courseList) {
                                    newSquare = aCourseList;
                                    if ((newSquare.getX() == opponentState.getX()) && (newSquare.getY() == opponentState.getY())) {
                                        if (GenericFlags.debugMessages) {
                                            log.debug("Will not step into opponents square");
                                        }
                                        //set the attack sprite
                                        CombatGlobals.combatGrid.setAttackState();
                                        meleeAttack(characterState, opponentState, longsword);
                                        //restore the default spreite
                                        CombatGlobals.combatGrid.setDefaultState();
                                    } else if (CombatGlobals.combatGrid.enemyOccupiedSquare(newSquare)) {
                                        if (GenericFlags.debugMessages) {
                                            log.debug("Will not step into an unfriendly occupied square");
                                        }
                                    } else if (CombatGlobals.combatGrid.friendlyOccupiedSquare(newSquare)) {
                                        if (GenericFlags.debugMessages) {
                                            log.debug("Will not step into an a friendly occupied square");
                                        }
                                    } else {
                                        CombatGlobals.combatGrid.movePlayer(characterState, newSquare);
                                        characterState.setXY(newSquare);
                                        characterState.setSurroundingSquares(area.getSurroundingSquares(newSquare));
                                        repaint();
                                        if (GenericFlags.debugMessages) {
                                            log.debug("Setting " + character.getCharacterName() + "'s placement to " + newSquare);
                                        }
                                        try {
                                            Thread.sleep(100);
                                        } catch (InterruptedException e) {
                                            if (GenericFlags.errorMessages){
                                                log.error(e.getMessage());
                                            }
                                        }
                                        //check range and attack if possible
                                        if (area.isInMeleeRange(characterState.getXY(), opponentState.getXY(), opponentState.getSurroundingSquares())) {
                                            CombatGlobals.combatGrid.setAttackState();
                                            meleeAttack(characterState, opponentState, longsword);
                                            CombatGlobals.combatGrid.setDefaultState();
                                        } else {
                                            if (GenericFlags.debugMessages) {
                                                log.debug("Not in range yet....");
                                                
                                            }
                                        }
                                        try {
                                            Thread.sleep(100);
                                        } catch (InterruptedException e) {
                                            if (GenericFlags.errorMessages){
                                                log.error(e.getMessage());
                                            }
                                        }
                                        
                                    }
                                }
                            } else {
                                //once character is directly adjacent to his opponent, attack
                                if (GenericFlags.debugMessages) {
                                    log.debug(character.getCharacterName() + " makes an attack!");
                                }
                                CombatGlobals.combatGrid.setAttackState();
                                meleeAttack(characterState, opponentState, longsword);
                                CombatGlobals.combatGrid.setDefaultState();
                                
                                try {
                                    Thread.sleep(100);
                                } catch (InterruptedException e) {
                                    if (GenericFlags.errorMessages){
                                        log.error(e.getMessage());
                                    }
                                }
                                
                            }
                        } else {
                            
                            //find a new opponent
                            long opponentId = chooseOpponent(characterState);
                            characterState.setOpponentId(opponentId);
                            opponentState = Combat.getOpposingCharacterState(characterState, combatants);
                            
                            if (characterState.getOpponentId() > 0) {
                                //determine if the character should move before making an attack
                                if (!area.isInMeleeRange(characterState.getXY(), opponentState.getXY(), opponentState.getSurroundingSquares())) {
                                    if (GenericFlags.debugMessages) {
                                        log.debug(character.getCharacterName() + " needs to move before making an attack");
                                    }
                                    
                                    //plot a course to the opponent
                                    courseList = this.course.plot(characterState.getXY(), opponentState.getXY(), blockedSquares);
                                    
                                    //loop through the course, setting the characters placement with each "step"
                                    for (Point aCourseList : courseList) {
                                        newSquare = aCourseList;
                                        if ((newSquare.getX() == opponentState.getX()) && (newSquare.getY() == opponentState.getY())) {
                                            if (GenericFlags.debugMessages) {
                                                log.debug("Will not step into opponents square");
                                            }
                                            CombatGlobals.combatGrid.setAttackState();
                                            meleeAttack(characterState, opponentState, longsword);
                                            CombatGlobals.combatGrid.setDefaultState();
                                            
                                        } else if (CombatGlobals.combatGrid.enemyOccupiedSquare(newSquare)) {
                                            if (GenericFlags.debugMessages) {
                                                log.debug("Will not step into an unfriendly occupied square");
                                            }
                                        } else if (CombatGlobals.combatGrid.friendlyOccupiedSquare(newSquare)) {
                                            if (GenericFlags.debugMessages) {
                                                log.debug("Will not step into an a friendly occupied square");
                                            }
                                        } else {
                                            CombatGlobals.combatGrid.movePlayer(characterState, newSquare);
                                            characterState.setXY(newSquare);
                                            characterState.setSurroundingSquares(area.getSurroundingSquares(newSquare));
                                            if (GenericFlags.debugMessages) {
                                                log.debug("Setting " + character.getCharacterName() + "'s placement to " + newSquare);
                                            }
                                            repaint();
                                            try {
                                                Thread.sleep(100);
                                            } catch (InterruptedException e) {
                                                if (GenericFlags.errorMessages){
                                                    log.error(e.getMessage());
                                                }
                                            }
                                            //check range and attack if possible
                                            if (area.isInMeleeRange(characterState.getXY(), opponentState.getXY(), opponentState.getSurroundingSquares())) {
                                                CombatGlobals.combatGrid.setAttackState();
                                                meleeAttack(characterState, opponentState, longsword);
                                                CombatGlobals.combatGrid.setDefaultState();
                                                
                                            } else {
                                                if (GenericFlags.debugMessages) {
                                                    log.debug("Not in range yet....");
                                                }
                                            }
                                            try {
                                                Thread.sleep(100);
                                            } catch (InterruptedException e) {
                                                if (GenericFlags.errorMessages){
                                                    log.error(e.getMessage());
                                                }
                                            }
                                            
                                            
                                        }
                                    }
                                    
                                    //if the character is on the last iteration of his movement, recalculate his surrounding squares
                                } else {
                                    //once character is directly adjacent to his opponent, attack
                                    CombatGlobals.combatGrid.setAttackState();
                                    meleeAttack(characterState, opponentState, longsword);
                                    CombatGlobals.combatGrid.setDefaultState();
                                    
                                    try {
                                        Thread.sleep(100);
                                    } catch (InterruptedException e) {
                                        if (GenericFlags.errorMessages){
                                            log.error(e.getMessage());
                                        }
                                    }
                                    
                                }
                            } else {
                                if (GenericFlags.debugMessages) {
                                    log.debug(character.getCharacterName() + " has no more opponents!");
                                }
                                try {
                                    Thread.sleep(100);
                                } catch (InterruptedException e) {
                                    if (GenericFlags.errorMessages){
                                        log.error(e.getMessage());
                                    }
                                }
                                
                            }
                        }
                    } else {
                        //Plot a course to retreat and make those steps
                        courseList = this.course.plotRetreat(characterState.getXY(), blockedSquares);
                        
                        //loop through the course, setting the characters placement with each "step"
                        for (Point aCourseList : courseList) {
                            newSquare = aCourseList;
                            characterState.setXY(newSquare);
                            characterState.setSurroundingSquares(area.getSurroundingSquares(newSquare));
                            if (GenericFlags.debugMessages) {
                                log.debug(character.getCharacterName() + " is panicked!  Retreating to square: " + characterState.getCharacter().getCharacterName() + "'s placement to " + newSquare);
                            }
                        }
                        
                    }
                    
                } else {
                    //display a message if the player is blind, held, or flat footed
                    if (characterState.isBlind()) {
                        if (GenericFlags.debugMessages) {
                            log.debug(character.getCharacterName() + " is blind!");
                        }
                    }
                    
                    if (characterState.isHeld()) {
                        if (GenericFlags.debugMessages) {
                            log.debug(character.getCharacterName() + " is held!");
                        }
                    }
                    
                    if (characterState.isFlatFooted()) {
                        if (GenericFlags.debugMessages) {
                            log.debug(character.getCharacterName() + " is flat footed");
                        }
                    }
                    
                    //  ;)
                    if (characterState.isBlind() && characterState.isHeld() && characterState.isFlatFooted()) {
                        if (GenericFlags.debugMessages) {
                            log.debug(character.getCharacterName() + " is screwed");
                        }
                    }
                    
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                if (GenericFlags.errorMessages){
                    log.error(e.getMessage());
                }
            }
            
        } while (!Combat.isEitherFactionDefeated(combatants));
        if (GenericFlags.debugMessages) {
            log.debug("Combat Complete!");
        }
    }
    
    /**
     * Runs an meleeAttack process for a state
     *
     * @param state
     */
    private void meleeAttack(CombatState state, CombatState opponent, Item weapon) {
        int attackRoll;
        int damageRoll;
        boolean isCritical = false;
        
        Weapon longsword = (Weapon) weapon;
        try {
            //Set up the intance objects;
            //this.state = state.getCharacter();
            
            //calculate the bonus
            int bonus = state.getCharacter().getBaseAttack() + longsword.getBaseAttackVal();
            
            //make the attack roll
            
            attackRoll = Dice.roll(20, 1) + bonus;
            
            //If the attack roll scored a successful hit.
            if (attackRoll >= opponent.getCharacter().getArmorClass()) {
                
                //if the attack roll is a 20, roll it again to determine if it's a critical hit.
                if (attackRoll == 20) {
                    if (Dice.roll(20, 1) + state.getCharacter().getBaseAttack() >= opponent.getCharacter().getArmorClass()) {
                        //this was a isCritical hit
                        isCritical = true;
                    }
                }
                
                //now roll damage
                damageRoll = (Dice.roll(longsword.getDamageDieSides(),
                    longsword.getDamageDie())) + state.getCharacter().getStrengthMod();
                
                //if the roll was a true critical, multiply the damage by two.
                if (isCritical) {
                    damageRoll = damageRoll + damageRoll;
                    if (GenericFlags.debugMessages) {
                        log.debug(state.getCharacter().getCharacterName() + " scored a critical hit with an attack roll of " + attackRoll + " " +
                            "vs " + opponent.getCharacter().getCharacterName() + "'s AC of " + opponent.getCharacter().getArmorClass() + ", " +
                            "doing " + damageRoll + " " +
                            "points of damage to " + opponent.getCharacter().getCharacterName());
                    }
                } else {
                    if (GenericFlags.debugMessages) {
                        log.debug(state.getCharacter().getCharacterName() + " hit with an attack roll of " + attackRoll + " " +
                            "vs " + opponent.getCharacter().getCharacterName() + "'s AC of " + opponent.getCharacter().getArmorClass() + ", " +
                            "doing " + damageRoll + " " +
                            "points of damage to " + opponent.getCharacter().getCharacterName());
                    }
                }
                
                //subtract damage from opposing player
                opponent.getCharacter().setCharacterHP(opponent.getCharacter().getCharacterHP() - damageRoll);
                if (GenericFlags.debugMessages) {
                    log.debug(opponent.getCharacter().getCharacterName() + " now has " + opponent.getCharacter().getCharacterHP() + " hit points left");
                }
                //test to see if the opposing player should die
                if (opponent.getCharacter().getCharacterHP() <= 0) {
                    opponent.getCharacter().setIsDead(true);
                    if (GenericFlags.debugMessages) {
                        log.debug(opponent.getCharacter().getCharacterName() + " is dead!");
                    }
                    CombatGlobals.combatGrid.clearDeadPlayer(opponent);
                    repaint();
                }
                
            } else {
                if (GenericFlags.debugMessages) {
                    log.debug(state.getCharacter().getCharacterName() + " rolled a " + attackRoll + " " +
                        "vs " + opponent.getCharacter().getCharacterName() + " AC of " + opponent.getCharacter().getArmorClass() + " and missed");
                    
                }
            }
        } catch (Exception e) {
            if (GenericFlags.errorMessages){
                log.error("An error was generated while attacking!" + e.getMessage());
            }
            
        }
    }
    
    /**
     * Sets the current character, (either hero or enemy) to select a new opponent
     * whos team is opposite of his
     *
     * @param character
     * @return
     */
    private long chooseOpponent(CombatState character) {
        
        long retVal = 0;
        ArrayList<CombatState> charactersToChooseFrom;
        
        try {
            if (character.isEnemy()) {
                if (GenericFlags.debugMessages) {
                    log.debug(character.getCharacter().getCharacterName() + " is looking for a hero to kill!");
                }
                charactersToChooseFrom = Combat.extractHeroesAsList(combatants);
                
                if (charactersToChooseFrom.size() > 0) {
                    if (GenericFlags.debugMessages) {
                        log.debug(character.getCharacter().getCharacterName() + " has " + charactersToChooseFrom.size() + " heros to choose from");
                    }
                    
                    //TODO:  Remove the random selection and choose an opponent who is closest.?
                    int randomRoll = Dice.roll(charactersToChooseFrom.size(), 1);
                    if (GenericFlags.debugMessages) {
                        log.debug("He chose the " + randomRoll + " option in the list");
                    }
                    CombatState charToAttack = charactersToChooseFrom.get(randomRoll - 1);
                    if (GenericFlags.debugMessages) {
                        log.debug("The character to attack is " + charToAttack.getCharacter().getCharacterName());
                    }
                    retVal = charToAttack.getCharacter().getId();
                    
                    if (GenericFlags.debugMessages) {
                        log.debug("The " + randomRoll + " person in the list happens to be ID# " + retVal);
                        log.debug(character.getCharacter().getCharacterName() + " chose " + charToAttack.getCharacter().getCharacterName() + " as an opponent");
                    }
                } else {
                    if (GenericFlags.debugMessages) {
                        log.debug("There are no more heros to kill");
                    }
                }
            } else if (!character.isEnemy()) {
                if (GenericFlags.debugMessages) {
                    log.debug(character.getCharacter().getCharacterName() + " is looking for an enemy to kill!");
                }
                charactersToChooseFrom = Combat.extractEnemiesAsList(combatants);
                if (charactersToChooseFrom.size() > 0) {
                    if (GenericFlags.debugMessages) {
                        log.debug(character.getCharacter().getCharacterName() + " has " + charactersToChooseFrom.size() + " enemies to choose from");
                    }
                    int randomRoll = Dice.roll(charactersToChooseFrom.size(), 1);
                    if (GenericFlags.debugMessages) {
                        log.debug("He chose the " + randomRoll + " option in the list");
                    }
                    CombatState charToAttack = charactersToChooseFrom.get(randomRoll - 1);
                    if (GenericFlags.debugMessages) {
                        log.debug("The character to attack is " + charToAttack.getCharacter().getCharacterName());
                    }
                    
                    retVal = charToAttack.getCharacter().getId();
                    if (GenericFlags.debugMessages) {
                        log.debug("The " + randomRoll + " person in the list happens to be ID# " + retVal);
                    }
                    if (GenericFlags.debugMessages) {
                        log.debug(character.getCharacter().getCharacterName() + " chose " + charToAttack.getCharacter().getCharacterName() + " as an opponent");
                    }
                } else {
                    if (GenericFlags.debugMessages) {
                        log.debug("There are no more enemies to kill");
                    }
                }
            } else {
                if (GenericFlags.debugMessages) {
                    log.debug(character.getCharacter().getCharacterName() + "doesnt know who to fight");
                }
            }
        } catch (Exception e) {
            if (GenericFlags.errorMessages){
                log.error("An error occured while choosing a new opponent!" + e.getStackTrace().toString());
            }
            
        }
        return retVal;
    }
    
    /**
     * External facing runtime
     *
     * @param args
     */
    public static void main(String[] args) {
        //new PrimitiveCombatRunner().runCombat();
    }
}